#include "common.h"
#include "sc.h"

using namespace std;
using namespace cg;

///////////////////////////////////////////////////////////////////////////////
//BranchPoint
///////////////////////////////////////////////////////////////////////////////

float BranchPoint::MinArea;

///////////////////////////////////////////////////////////////////////////////
//Tree
///////////////////////////////////////////////////////////////////////////////

void Tree::Render( void )
{
	glColor3ub( 0, 255, 0 );
	glColor3ub( 139, 131, 120 );	
	for( Segments::const_iterator it = segments.begin(); it != segments.end(); ++it ) {	
		float r = sqrt( branchPoints[ it->first ].area );
		glLineWidth( r );
		glBegin( GL_LINES );	
			Vec3 A = branchPoints[ it->first ].position;
			Vec3 B = branchPoints[ it->second ].position;		
			glVertex3f( A.x, A.y, A.z );
			glVertex3f( B.x, B.y, B.z );
		glEnd();
	}	
}

int Tree::FindIndexOfClosestBranchPoint( const Vec3& position ) const
{
	float minDist = FLT_MAX;
	int index = -1;
	for( IndexedBranchPoints::const_iterator it = branchPoints.begin(); it != branchPoints.end(); ++it ) {
		float distSq = it->second.position.DistSq( position );
		if ( distSq < minDist) {
			minDist = distSq;
			index = it->first;
		}
	}
	assert( index != -1 );
	return index;
}

bool Tree::Update( float D )
{	
	bool retValue = false;
	for( IndexedBranchPoints::iterator it = branchPoints.begin(); it != branchPoints.end(); ++it ) {
		BranchPoint &branchPoint = it->second;
		int index1 = it->first;
		// branch point should grow
		if ( branchPoint.growCount > 0 ) {			
			branchPoint.growDirection /=  static_cast<float>( branchPoint.growCount );			
			if ( branchPoint.growDirection.LenSq() > 0.001f ) {				
				retValue = true;
				//
				branchPoint.growDirection.Normalize();			
				int index2 = AddBranchPoint( index1, branchPoint.position + branchPoint.growDirection * D );
				AddSegment( index1, index2 );				
			}			
			branchPoint.ResetGrow();
		}
	}
	return retValue;
}

int Tree::AddBranchPoint( int parentIndex, const Vec3& position )
{
	int index;
	int hops = 0;
	if ( parentIndex != -1 )
		hops = this->GetBranchPoint( parentIndex ).hopsFromBase + 1;
	BranchPoint bp( parentIndex, hops );
	//	
	bp.ResetGrow();
	bp.position = position;
	index = GetIndexOfLastBranchPoint() + 1;
	branchPoints.insert( make_pair( index, bp ) );
	return index;
}

// moving each remaining node in parallel half way toward its more basal neighbour
// reduces the branching angles
void Tree::ReduceBranchingAngle( void )
{
	IndexedBranchPoints		newBranchPoints;
	// add first node
	newBranchPoints.insert( *branchPoints.begin() );	
	//
	for( Segments::const_iterator it = segments.begin(); it != segments.end(); ++it ) {	
		BranchPoint A = branchPoints[ it->first ];
		BranchPoint B = branchPoints[ it->second ];				
		Vec3 dir = A.position - B.position;
		BranchPoint newPoint = B;
		newPoint.position += dir * 0.5f;
		newBranchPoints.insert( make_pair( it->second, newPoint ) );
	}
	// use newly computed nodes
	branchPoints = newBranchPoints;
}

void Tree::ComputeLevel( void )
{
	IndexedBranchPoints	leaves = branchPoints;
	// find leaves
	for( Segments::const_iterator it = segments.begin(); it != segments.end(); ++it ) {	
		leaves.erase( it->first );
	}
	
	for( IndexedBranchPoints::const_iterator it = leaves.begin(); it != leaves.end(); ++it ) {			
		IncrementBranch( it->first );
	}	
}

void Tree::IncrementBranch( int index )
{
	float addArea = branchPoints[ index ].area;
	int parentIndex;
	//	
	while( ( parentIndex = branchPoints[ index ].parent ) != -1 ) {		
		branchPoints[ parentIndex ].area += addArea;		
		index = parentIndex;
	}
}

///////////////////////////////////////////////////////////////////////////////
//Envelope
///////////////////////////////////////////////////////////////////////////////

Envelope::Envelope( float radius, const cg::Vec3& position, int num )
{	
	this->radius = radius;
	float r = radius;	
	number = num;
	for( int i = 0; i < number; ++i ) {	
		Vec3 v( Random::Rand( -r, r), Random::Rand( -r, r), Random::Rand( -r, r) );
		if ( v.LenSq() < r*r ) {
			AtractionPoint ap;
			ap.position = v + position;
			atractionPoints.push_back( ap );
		} else {
			i++;
		}
	}
}

void Envelope::Render()
{       
    glPushMatrix();        
        glColor3ub( 0, 255, 0 );
        glPointSize( 3.0f );
        glBegin( GL_POINTS );

		AtractionPoints::iterator it = GetAtractionPointsBegin();
		while( it != GetAtractionPointsEnd() ) {
			glVertex3f( it->position.x, it->position.y, it->position.z );
			++it;
        }
        glEnd();
    glPopMatrix();
}

///////////////////////////////////////////////////////////////////////////////
//Compute grow for one iteration
///////////////////////////////////////////////////////////////////////////////

void ComputeGrewIteration( Envelope &envelope,
						   Tree &tree,
						   float D,
						   float infDistMultOfD,
						   float killDistMultOfD )
{
	float killDistSq = killDistMultOfD == FLT_MAX ? FLT_MAX : ( D * killDistMultOfD ) * ( D * killDistMultOfD );
	float infDistSq = infDistMultOfD == FLT_MAX ? FLT_MAX : ( D * infDistMultOfD ) * ( D * infDistMultOfD );

	AtractionPoints::iterator it = envelope.GetAtractionPointsBegin();
	while( it != envelope.GetAtractionPointsEnd() ) {

		int index = tree.FindIndexOfClosestBranchPoint( it->position );
		BranchPoint &branchPoint = tree.GetBranchPoint( index );				
		// atraction point is NOT too far to have influence
		if ( branchPoint.position.DistSq( it->position ) < infDistSq ) {
			Vec3 dir = it->position - branchPoint.position;
			dir.Normalize();				
			// special case - to avoid situation where aps nulls themselves
			if ( branchPoint.growCount != 1 ||
				branchPoint.growDirection.DotProduct( dir ) > -0.7f ) {							
				it->closestBranchIndex = index;
				++branchPoint.growCount;
				branchPoint.growDirection += dir;
			}				
		} 
		// atraction point is too close and will be removed
		if ( branchPoint.position.DistSq( it->position ) < killDistSq ) {
			it = envelope.RemoveAtractionPoint( it );
		} else {		
			++it;
		}				
	}
}

///////////////////////////////////////////////////////////////////////////////
// Generation of tree
///////////////////////////////////////////////////////////////////////////////

int iteration = 500;
bool treeUpdate = true;

// called repeatedly to display progress
void BuildTreeDebug(	Envelope &envelope,
						Tree &tree,
						float D,
						float infDistMultOfD,
						float killDistMultOfD)
{		
	if( treeUpdate && iteration > 0 ) {
		iteration--;
		ComputeGrewIteration( envelope, tree, D, infDistMultOfD, killDistMultOfD );
		treeUpdate = tree.Update( D );
		if ( ! treeUpdate ) {
			int smoooth = 4;
			while ( --smoooth )
				tree.ReduceBranchingAngle();
			tree.ComputeLevel();
		}
	}
	envelope.Render();
	tree.Render();	
}

void BuildTree(	Envelope &envelope,
				Tree &tree,
				float D,
				float infDistMultOfD,
				float killDistMultOfD,				
				int levelOfSmoothing )
{		
	//
	treeUpdate = true;
	int iteration = 500;
	while( treeUpdate && iteration > 0 ) {
		iteration--;
		ComputeGrewIteration( envelope, tree, D, infDistMultOfD, killDistMultOfD );
		treeUpdate = tree.Update( D );		
	}	
	
	while ( --levelOfSmoothing > 0 )
		tree.ReduceBranchingAngle();
	tree.ComputeLevel();	
}

///////////////////////////////////////////////////////////////////////////////
// Generation of trunk
///////////////////////////////////////////////////////////////////////////////

void CreateTrunk( Tree &tree,
				  float D, 
				  float totalLen )
{	
	Vec3 source =  Vec3::ZERO;
	Vec3 dir = Vec3::Y_AXIS * D;
	int indexSource  = tree.AddBranchPoint( -1, source );	
	//
	int segments = static_cast<int>( totalLen / D );
	for( int i = 0; i < segments; ++i ) {
		Vec3 dest = source + dir;// + Vec3::X_AXIS * 0.1 * i;
		int indexDest = tree.AddBranchPoint( indexSource, dest );
		tree.AddSegment( indexSource, indexDest );
		//
		indexSource = indexDest;
		source = dest;
	}
}